package com.tools.common.security;

import com.tools.common.exception.IORuntimeException;
import com.tools.common.object.BasicEnum;
import com.tools.common.object.Note;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * NIO 读取网络发送的数据时简化操作的包装类
 * 该类不是线程安全的，不能用于多个用途
 * 只能用于从 SocketChannel 中读数据
 * */
@Note("NIO 读取网络发送的数据时简化操作的包装类。" +
        "该类不是线程安全的，不能用于多个用途，" +
        "只能用于从 SocketChannel 中读数据")
public final class NetBufferReader {

    @Note("读取的数据就存在这")
    private byte[] data;

    @Note("读取到的数据的字节数")
    private int size;

    @Note("中间媒介 ByteBuffer")
    private final ByteBuffer byteBuffer;

    /* *********************************************************************
     *
     *          构造器
     *
     * *********************************************************************
     * */

    public NetBufferReader(int bufferSize) {
        if(bufferSize <= 4) throw new IllegalArgumentException("ByteBuffer 的长度必须大于 4，前 4 个字节用于存放长度");
        this.byteBuffer = ByteBuffer.allocate(bufferSize);
    }

    public static NetBufferReader of(int bufferSize) {
        return new NetBufferReader(bufferSize);
    }

    /* *********************************************************************
     *
     *          其他方法
     *
     * *********************************************************************
     * */

    public int getSize() {
        return size;
    }

    public byte[] getData() {
        byte[] result = data;
        this.data = null;
        this.size = 0;
        return result;
    }

    public String getDataToString() {
        byte[] data = this.getData();
        return (data == null || data.length == 0) ? "" : new String(data);
    }

    public NetBufferReader read(SocketChannel socketChannel) {
        try {
            socketChannel.read(byteBuffer);
            byteBuffer.flip();
            int length = Math.abs(BasicEnum.byteArrayToInt(new byte[]{byteBuffer.get(), byteBuffer.get(),
                    byteBuffer.get(), byteBuffer.get()}));
            int v = byteBuffer.capacity() - 4;
            int n = length / v;
            int m = length % v;
            int count = (m == 0) ? n : n + 1;
            private_handleData(length, byteBuffer);
            byteBuffer.clear();
            for (int i = 0; i < count - 1; i++) {
                socketChannel.read(byteBuffer);
                byteBuffer.flip();
                private_handleData(length, byteBuffer);
                byteBuffer.clear();
            }
            return this;
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }

    /* *********************************************************************
     *
     *          静态方法
     *
     * *********************************************************************
     * */

    @Note("合并两个字节数组。第一个是内容长度的字节数组，数组长度为 4。第二个是装着发送数据的字节数组")
    public static byte[] concat(byte[] length, byte[] data) {
        int b1Len = length.length;
        int b2Len = data.length;
        byte[] res = new byte[b1Len + b2Len];
        System.arraycopy(length, 0, res, 0, b1Len);
        System.arraycopy(data, 0, res, b1Len, b2Len);
        return res;
    }

    @Note("将字符串转为 【内容长度 + 内容数据】 的 ByteBuffer 缓冲区实例")
    public static ByteBuffer encode(String data) {
        if(data == null || data.isEmpty()) throw new IllegalArgumentException("不能发送空的数据");
        byte[] dataBytes = data.getBytes();
        byte[] lengthArray = BasicEnum.intToByteArray(dataBytes.length);
        return ByteBuffer.wrap(concat(lengthArray, dataBytes));
    }

    @Note("将要发送的数据的内容字节数组转为 【内容长度 + 内容数据】 的 ByteBuffer 缓冲区实例")
    public static ByteBuffer encode(byte[] data) {
        if(data == null || data.length == 0) throw new IllegalArgumentException("不能发送空的数据");
        byte[] lengthArray = BasicEnum.intToByteArray(data.length);
        return ByteBuffer.wrap(concat(lengthArray, data));
    }


    /* *********************************************************************
     *
     *          私有逻辑
     *
     * *********************************************************************
     * */

    @Note("读取 ByteBuffer 内部的数据并封装到 data 字节数组里")
    private void private_handleData(int finalLen, ByteBuffer buffer) {
        if(data == null) {
            data = new byte[finalLen];
            int count = 0;
            for (int i = 0, j = buffer.position(); j < buffer.limit(); i++, j++) {
                data[i] = buffer.get();
                count++;
            }
            size += count;
            return;
        }
        int limit = buffer.limit();
        int count = 0;
        for (int i = size, j = buffer.position(); j < limit; i++, j++) {
            data[i] = buffer.get();
            count++;
        }
        size += count;
    }


}
